package com.naivecoders.refex;

import com.naivecoders.refex.functions.AbstractFunction;
import com.naivecoders.refex.functions.FCode;
import com.naivecoders.refex.functions.ListFunction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FunctionFactory {

    private final Namespace namespace;
    private final ListFunction emptyList = new ListFunction(this);
    private final CallTrail trail  = new CallTrail();
    private final Map<FCode, AbstractFunction> instances = new HashMap<>();
    private final Map<AbstractFunction, FCode> rInstances = new HashMap<>();

    public FunctionFactory(Namespace namespace) {
        this.namespace = namespace;
        initElementary();
    }

    public AbstractFunction elementary(FCode c) {
        return instances.get(c);
    }

    private void initElementary() {
        for (FCode c : FCode.values()) {
            AbstractFunction f = c.create(this);
            if (f != null) {
                instances.put(c, f);
                rInstances.put(f, c);
            }
        }
    }

    public AbstractFunction trueFunction() {
        return elementary(FCode.T);
    }

    public AbstractFunction invoke(AbstractFunction what, List<AbstractFunction> args) {
        final List<AbstractFunction> invoke = new ArrayList<>();
        invoke.add(elementary(FCode.I));
        invoke.add(what);
        invoke.addAll(args);
        return list(invoke);
    }

    public FCode getCode(AbstractFunction func) {
        return rInstances.get(func);
    }

    public ListFunction emptyList() {
        return emptyList;
    }

    public ListFunction argument(FName name) {
        return new ListFunction(this, convert(name));
    }

    public ListFunction list(List<AbstractFunction> abstractFunctions) {
        return new ListFunction(this, abstractFunctions);
    }

    public ListFunction list(AbstractFunction... abstractFunctions) {
        return new ListFunction(this, abstractFunctions);
    }

    public Namespace getNamespace() {
        return namespace;
    }

    public void log(final String msg) {
        System.out.println(msg);
    }

    public AbstractFunction substitudeArguments() {
        return elementary(FCode.S);
    }

    public AbstractFunction invoke() {
        return elementary(FCode.I);
    }

    /**
     * Produces invocation of named function with name as an argument
     *
     * @return
     */
    public AbstractFunction resolve(FName name) {
        final List<AbstractFunction> convertedName = convert(name);
        // first find the function to invoke
        final AbstractFunction func = invoke(elementary(FCode.R),
                convertedName);

        return func;
    }

    private List<AbstractFunction> convert(FName name) {
        final List<AbstractFunction> convertedName = new ArrayList<>();
        for (FCode efn : name.asArray()) {
            convertedName.add(elementary(efn));
        }
        return convertedName;
    }

    public CallTrail getTrail() {
        return trail;
    }
}
